---
menu: Guides
title: Server Side Rendering
order: 65
---

# Server Side Rendering

## Install

```bash
npm install @loadable/server && npm install --save-dev @loadable/babel-plugin @loadable/webpack-plugin
# or using yarn
yarn add @loadable/server && yarn add --dev @loadable/babel-plugin @loadable/webpack-plugin
```

## Guide

### 1. Install `@loadable/babel-plugin`

**.babelrc**

```json
{
  "plugins": ["@loadable/babel-plugin"]
}
```

### 2. Install `@loadable/webpack-plugin`

**webpack.config.js**

```js
const LoadablePlugin = require('@loadable/webpack-plugin')

module.exports = {
  // ...
  plugins: [new LoadablePlugin()],
}
```

### 3. Setup `ChunkExtractor` server-side

```js
import { ChunkExtractor } from '@loadable/server'

// This is the stats file generated by webpack loadable plugin
const statsFile = path.resolve('../dist/loadable-stats.json')

// We create an extractor from the statsFile
const extractor = new ChunkExtractor({ statsFile })

// Wrap your application using "collectChunks"
const jsx = extractor.collectChunks(<YourApp />)

// Render your application
const html = renderToString(jsx)

/* `renderToString` must be called before `getScriptTags`, `getLinkTags`
 * and `getStyleTags`.
 */

// You can now collect your script tags
const scriptTags = extractor.getScriptTags() // or extractor.getScriptElements();

// You can also collect your "preload/prefetch" links
const linkTags = extractor.getLinkTags() // or extractor.getLinkElements();

// And you can even collect your style tags (if you use "mini-css-extract-plugin")
const styleTags = extractor.getStyleTags() // or extractor.getStyleElements();
```

### 4. Add `loadableReady` client-side

Loadable components loads all your scripts asynchronously to ensure optimal performances. All scripts are loaded in parallel, so you have to wait for them to be ready using `loadableReady`.

```js
import { loadableReady } from '@loadable/component'

loadableReady(() => {
  const root = document.getElementById('main')
  hydrate(<App />, root)
})
```

**🚀 [Checkout the complete example in this repository](https://github.com/gregberge/loadable-components/tree/master/examples/server-side-rendering)**

## Collecting chunks

The basic API goes as follows:

```js
import { renderToString } from 'react-dom/server'
import { ChunkExtractor } from '@loadable/server'

const statsFile = path.resolve('../dist/loadable-stats.json')
const extractor = new ChunkExtractor({ statsFile })
const html = renderToString(extractor.collectChunks(<YourApp />))
const scriptTags = extractor.getScriptTags() // or extractor.getScriptElements();
```

The `collectChunks` method wraps your element in a provider. Optionally you can use the `ChunkExtractorManager` provider directly, instead of this method. Just make sure not to use it on the client-side.

```js
import { renderToString } from 'react-dom/server'
import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server'

const statsFile = path.resolve('../dist/loadable-stats.json')
const extractor = new ChunkExtractor({ statsFile })
const html = renderToString(
  <ChunkExtractorManager extractor={extractor}>
    <YourApp />
  </ChunkExtractorManager>,
)

const scriptTags = extractor.getScriptTags() // or extractor.getScriptElements();
```

The `extractor.getScriptTags()` returns a string of multiple `<script>` tags marked as "async". You have to wait for them to be ready using `loadableReady`.

Alternatively the `ChunkExtractor` also has a `getScriptElements()` method that returns an array of React elements.

## Streaming rendering

Loadable is compatible with streaming rendering, if you use it you have to include script when the stream is complete.

```js
import { renderToNodeStream } from 'react-dom/server'
import { ChunkExtractor } from '@loadable/server'

// if you're using express.js, you'd have access to the response object "res"

// typically you'd want to write some preliminary HTML, since React doesn't handle this
res.write('<html><head><title>Test</title></head><body>')

const statsFile = path.resolve('../dist/loadable-stats.json')
const chunkExtractor = new ChunkExtractor({ statsFile })
const jsx = chunkExtractor.collectChunks(<YourApp />)
const stream = renderToNodeStream(jsx)

// you'd then pipe the stream into the response object until it's done
stream.pipe(res, { end: false })

// and finalize the response with closing HTML
stream.on('end', () =>
  res.end(`${chunkExtractor.getScriptTags()}</body></html>`),
)
```

> Streaming rendering is not compatible with prefetch `<link>` tags.

## Prefetching

[Webpack prefetching](https://webpack.js.org/guides/code-splitting/#prefetching-preloading-modules) is supported out of the box by Loadable. [`<link rel="preload">` and `<link rel="prefetch">`](https://css-tricks.com/prefetching-preloading-prebrowsing/) can be added directly server-side to improve performances.

```js
import path from 'path'
import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server'

const statsFile = path.resolve('../dist/loadable-stats.json')
const extractor = new ChunkExtractor({ statsFile })

const jsx = extractor.collectChunks(<YourApp />)

const html = renderToString(jsx)

const linkTags = extractor.getLinkTags() // or chunkExtractor.getLinkElements();

const html = `<html>
  <head>${linkTags}</head>
  <body>
    <div id="root">${html}</div>
  </body>
</html>`
```

> It only works with `renderToString` API. Since `<link>` must be added in the `<head>`, you can't do it using `renderToNodeStream`.

## CSS

Extracted CSS using plugins like ["mini-css-extract-plugin"](https://github.com/webpack-contrib/mini-css-extract-plugin) are automatically collected, you can get them using `getStyleTags` or `getStyleElements`.

```js
import { renderToString } from 'react-dom/server'
import { ChunkExtractor } from '@loadable/server'

const statsFile = path.resolve('../dist/loadable-stats.json')
const extractor = new ChunkExtractor({ statsFile })
const html = renderToString(extractor.collectChunks(<YourApp />))
const styleTags = extractor.getStyleTags() // or extractor.getStyleElements();
```

## Disable SSR on a specific loadable

Disable SSR on a specific loadable component with `ssr: false`:

```js
import loadable from '@loadable/component'

// This dynamic import will not be processed server-side
const Other = loadable(() => import('./Other'), { ssr: false })
```

## Override `stats.publicPath` at runtime

To override `stats.publicPath` at runtime, pass in a custom `publicPath` to the `ChunkExtractor` constructor:

```js
import { ChunkExtractor } from '@loadable/server'

const statsFile = path.resolve('../dist/loadable-stats.json')

const extractor = new ChunkExtractor({
  statsFile,
  publicPath: 'https://cdn.example.org/v1.1.0/',
})
```

## `ChunkExtractor` entrypoints

When running your build, notice `@loadable/webpack-plugin` generates a file called `loadable-stats.json`, which contains information
about all your entries and chunks from webpack.

Once that's in place, `ChunkExtractor` will be responsible of finding your entries into this file.

The default behaviour of webpack, is to create an asset called `main.js` if no named entry is specified, like so.

**webpack.config.js**

```js
module.exports = {
  entry: './src/index.js',
  // ...
}
```

[Checkout webpack's entry naming configuration](https://webpack.js.org/configuration/entry-context/#naming).

> `ChunkExtractor` will try to find your `main.js`, and will look into `loadable-stats.json` to confirm it's there.

If for instance, your wish is to get a different named entry, you will need to pass an `entrypoints` option.

```js
const extractor = new ChunkExtractor({
  statsFile,
  entrypoints: ['client'], // array of webpack entries (default: ['main'])
})
```

## Using your own stats file

By default, the webpack plugin adds an asset to the webpack build called `loadable-stats.json`. This contains the result of running webpack's [`stats.toJson()`](https://webpack.js.org/api/node/#statstojsonoptions) with the following options:

```js
{
  hash: true,
  publicPath: true,
  assets: true,
  chunks: false,
  modules: false,
  source: false,
  errorDetails: false,
  timings: false,
}
```

`stats.toJson()` is an expensive operation, and it can significantly slow down webpack watching recompiles.
If you already have a webpack stats file in your build that includes the necessary options, you may choose to use your existing stats object instead of creating a new one. You can do this as follows:
 - pass your existing stats object into [`ChunkExtractor`](/docs/api-loadable-server/#chunkextractor) via the `stats` option
 - disable both the `outputAsset` and `writeToDisk` options in the [webpack plugin](/docs/api-loadable-webpack-plugin/#loadableplugin) to prevent it from calling `stats.toJson()`
